var fs=require('fs');

/*****************************************************************************/
function ModelBuilder(site,system,space)
{
	this.site=site;
	this.system=system;
	this.space=space;
}

/*****************************************************************************/
ModelBuilder.prototype.parse_models=function(config)
{
	var modeldefs;
	var out_file;
	var mcp;
	if (config["models-source-config"]) throw "--models-source-config is deprecated";
//	throw this.space.Dumper(this.site.paths);
	if (config.global) {
		mcp=this.system.paths.models_config;
	} else {
		mcp=this.site.paths.models_config;
	}
	modeldefs=require(mcp).build_models;
	out_file=mcp.replace(/\.js$/,'-parsed.js');
	var i=0;
	while (i<modeldefs.length) {
		var modeldef=modeldefs[i];
		if (this.model_prepare(modeldef)) {
			i++;
		} else {
			modeldefs.splice(i,1);
		}
	}
	this.save_file_if_changed(out_file,"exports.parsed_models="+JSON.stringify(modeldefs,undefined,1)+";\n");
}


/*****************************************************************************/
ModelBuilder.prototype.gen_models=function(config)
{
	if (config["models-parsed-config"]) throw "--models-parsed-config is deprecated";
	if (config["generate-path"]) throw "--generate-path is deprecated";
	var mcp;
	if (config.global) {
		mcp=this.system.paths.models_config;
		this.paths=this.system.paths;
	} else {
		mcp=this.site.paths.models_config;
		this.paths=this.site.paths;
	}
	mcp=mcp.replace(/\.js$/,'-parsed.js');
	modeldefs=require(mcp).parsed_models;


//	if (config["models-parsed-config"]) {
//		modeldefs=require(config["models-parsed-config"]).parsed_models;
//	} else {
//		throw "CHANGED";
//		modeldefs=require("../../config/models-parsed.js").parsed_models;
//	}
	for (var i=0;i<modeldefs.length;i++) {
		var modeldef=modeldefs[i];
		this.build_model_sql(modeldef);
		if (modeldef.admin_code) this.build_model_views(modeldef);
	}
	var filename;
	filename=this.paths.models+"_auto.js";
	this.save_file_if_changed(filename,this.build_all_models_js(modeldefs));
	
	filename=this.paths.controllers+"_auto.js";
	this.save_file_if_changed(filename,this.build_all_controllers_js(modeldefs));
}

/*****************************************************************************/
ModelBuilder.prototype.model_prepare=function(modeldef)
{
	if (!modeldef.table) modeldef.table="T"+modeldef.name+"s";
	if (!modeldef.name_many) modeldef.name_many=modeldef.name+"s";
	if (!modeldef.columns) modeldef.columns=this.fetch_table_columns(modeldef);
	if (modeldef.columns) return true; else return false;
//	if (!modeldef.admin_list_header) modeldef.admin_list_header="Записи "+modeldef.name+" в справочнике";
//	if (!modeldef.admin_edit_header) modeldef.admin_edit_header="Редактирование записи "+modeldef.name+" в справочнике";
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_views=function(modeldef)
{
	this.build_model_view_admin_list(modeldef);
	this.build_model_view_admin_show(modeldef,"edit");
}

/*****************************************************************************/
ModelBuilder.prototype.fill_columns_and_reorder=function(space,modeldef,mode)
{
	var list=modeldef["admin_"+mode+"_columns"];
	if (!list) return modeldef.columns;
	var moredata={};
	for (var i=0;i!=modeldef.columns.length;i++) {
		moredata[modeldef.columns[i].name]=modeldef.columns[i];
	}
	list.forEach(function(c) {
		if (!moredata[c.name]) return;
		for (k in moredata[c.name]) if (!(k in c)) c[k]=moredata[c.name][k];
	});
	return list;
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_view_admin_list=function(modeldef)
{
	space={
		modeldef:modeldef
	};
	modeldef.list_columns=[];
	modeldef.list_cells="";
	modeldef.list_has_sort=0;
	modeldef.label_title=modeldef.list_title||"{{!+title}}";
	modeldef.label_create=modeldef.list_create||"{{!+create}}";
	var edit_link_add="";
//	var gr="";
	var list=this.fill_columns_and_reorder(space,modeldef,"list");
/*	var list=modeldef.admin_list_columns||modeldef.columns;
	var moredata={};
	if (modeldef.admin_list_columns) {
		for (var i=0;i!=modeldef.columns.length;i++) {
			moredata[modeldef.columns[i].name]=modeldef.columns[i];
		}
	}*/
	for (var i=0;i!=list.length;i++) {
		var c=list[i];
		if (c.through) edit_link_add+="&"+c.name+"={{fields."+c.name+"}}";
	}
	modeldef.create_link_add=edit_link_add;
	for (var i=0;i!=list.length;i++) {
		var c=list[i];
/*		if (moredata[c.name]) {
			for (k in moredata[c.name]) if (!(k in c)) c[k]=moredata[c.name][k];
		}*/
		if (c.name=="ordering") modeldef.list_has_sort=1;
		if (c.skip_list || c.skip_admin_list) continue;
		if (c.name=="id") continue;
		var outname=c.name;
		if (c.fk) outname=outname.replace(/_id$/,'')+"_name";
		var h={name: c.name,label: c.list_label||c.label||"{{!+"+c.name+"}}",type: this.ajajform_grid_column_datatype(c)};
		if (c.list_width) h.width=c.list_width;
		if (modeldef.list_columns.length==0) h.url="{{action}}?mode=edit"+edit_link_add+"&id=";
		modeldef.list_columns.push(h);
//		if (gr.length) gr+=",";
//		gr+=outname;
		if (modeldef.list_cells) modeldef.list_cells+=",";
		switch (c.datatype) {
			case "file_id":
				modeldef.list_cells+="'<img src=\"'+mkpreview(item."+c.name+"_url,0)+'\" onmouseover=\"img_bigger_preview(this,\\'2\\')\" onmouseout=\"img_bigger_preview(this)\"/>'";
				h.html=1;
				break;
			case "html":
				modeldef.list_cells+="\""+this.space.functions.JS(this.space,c.html).replace(/\{\{row.(\w+)\}\}/,"\"+item.$1+\"")+"\"";
				h.html=1;
				break;
			default:
				modeldef.list_cells+="item."+outname;
				break;
		}
	}
//	space.gridrower_params=gr;
//	{name: "date_use", label: "Дата", width: 100},
	var html=this.space.views.process("system/auto_tmpl/admin_list",space);
//	html=html.replace(/\\}/g,'}').replace(/\\{/g,'{');
	this.save_view(modeldef,"admin_list",html);
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_view_admin_show=function(modeldef,mode)
{
	space={
		modeldef:modeldef
	};
	modeldef.show_inputs=[];
	modeldef.label_title=modeldef[mode+"_title"]||"{{!+title}}";
	modeldef.label_save=modeldef[mode+"_save"]||"{{!+save}}";
	modeldef.label_save_and_exit=modeldef[mode+"_save"]||"{{!+save_and_exit}}";
	modeldef.label_save_and_continue=modeldef[mode+"_save_and_continue"]||"{{!+save_and_continue}}";
	modeldef.label_delete=modeldef[mode+"_delete"]||"{{!+delete}}";
	modeldef.params={mode: modeldef.admin_mode_save||"save",id: '{{'+modeldef.name+'.id}}',ajaj: 1};
	
	var list=this.fill_columns_and_reorder(space,modeldef,mode);
	for (var i=0;i!=list.length;i++) {
		var c=list[i];
		if (c.through) modeldef.params[c.name]='{{OR({v1:'+modeldef.name+'.'+c.name+',v2:fields.'+c.name+'})}}';
		if (c['skip_'+mode]) continue;
		if (c.name=="id") continue;
		var h={
			type: this.ajajform_widget_type(c,mode),
			name: c.name,
			label: c.edit_label||c.label||"{{!+"+c.name+"}}",
			width: c.edit_width||600,
			value: (c.fields?"{{OR({v2:fields."+c.name+",v1:":"{{JS:")+modeldef.name+"."+c.name+(c.fields?"})":"")+"}}"
		};
		switch (c.datatype) {
			case "file_id":
				var t="{{JS:"+modeldef.name+"."+c.name;
				h.value={url: t+"_url}}",name: t+"_name_or_filename}}",id: t+"}}"};
				if (c.allow_upload) h.allowUpload=true;
				break;
			case "file_group_id":
				if (c.allow_upload) h.allowUpload=true;
				break;
			case "text":
				if (c.wysiwyg) h.wysiwyg=1;
				break;
		}
		if (h.type=="textedit") {
			if (c.not_null) h.minsize=1;
			if (c.datatype_size) h.maxsize=c.datatype_size;
		}
		if (c.fk && mode=="edit") {
			h.values=">>{{JSON:select_"+c.fk+"}}<<";
		}
		if (c.change && mode=="edit") {
			h.change=">>"+c.change+"<<";
		}
		modeldef.show_inputs.push(h);
	}
	if (modeldef[mode+"_footer"]) {
		for (var i=0;i<modeldef[mode+"_footer"].length;i++) {
			var fi=modeldef[mode+"_footer"][i];
			this.build_model_view_admin_show_footer_item(modeldef,mode,fi);
		}
	}
	var html="";
	if (modeldef["admin_"+mode+"_header"]) html+=this.space.views.process(modeldef["admin_"+mode+"_header"],space);
	html+=this.space.views.process("system/auto_tmpl/admin_"+mode,space);
	if (modeldef["admin_"+mode+"_footer"]) html+=this.space.views.process(modeldef["admin_"+mode+"_footer"],space);
	this.save_view(modeldef,"admin_"+mode,html);
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_view_admin_show_footer_item=function(modeldef,mode,fi)
{
	switch (fi.type) {
		case "checkboxlist":
			var h={
				type: fi.type,
				name: fi.name,
				label: fi.label||"{{!+"+fi.name+"}}",
				width: fi.width||600,
				available:">>{{JSON:"+fi.name+".available}}<<",
				checked:">>{{JSON:"+fi.name+".checked}}<<"
			};
			modeldef.show_inputs.push(h);
			break;
		case "function":
			if (!this.space.functions[fi.name]) {
				throw "No function "+fi.name;//+" in\n"+this.space.Dumper(this.space.functions);
			}
			this.space.functions[fi.name](space,"build",{modeldef:modeldef,mode:mode,fi:fi});
			break;
	}
}

/*****************************************************************************/
ModelBuilder.prototype.ajajform_grid_column_datatype=function(col)
{
	switch (col.datatype) {
		case "checkbox":
			return "checkbox";
	}
	return "text";
}

/*****************************************************************************/
ModelBuilder.prototype.ajajform_widget_type=function(col,mode)
{
	if (col['readonly']) return "label";
	if (col['readonly_'+mode]) return "label";
	if (col.datatype=='date') return 'date';
	if (col.datatype=='checkbox') return 'checkbox';
	if (col.datatype=='text') return 'textareaedit';
	if (col.datatype=='file_id') return 'file_id';
	if (col.datatype=='file_group_id') return 'file_group_id';
	if (col.fk && mode=="edit") return 'select';
	return "textedit";
}

/*****************************************************************************/
ModelBuilder.prototype.save_view=function(modeldef,mode,htmlcode)
{
	var basepath=this.paths.views+"_models/";
	var filename=modeldef.name+"/"+mode+".html";
	this.space.functions.mkdir_rec(this.paths.views,"_models/"+filename,1);
	var f=new fs.File(basepath+filename);
	f.open("w");
	htmlcode=htmlcode.replace(/\<\{\>/g,"{{").replace(/\<\}\>/g,"}}").replace(/">>/g,"").replace(/<<"/g,"").replace(/\<\\"\>/g,'"');
	f.write(htmlcode);
	f.close();
}

/*****************************************************************************/
ModelBuilder.prototype.build_all_models_js=function(modeldefs)
{
	var jscode="";
	jscode+="exports.models=function(GenericModel) {\n";
	jscode+="	var r={};\n";
	for (var i=0;i<modeldefs.length;i++) {
		var modeldef=modeldefs[i];
		jscode+=this.build_model_js(modeldef,modeldefs);
	}
	jscode+="	return r;\n";
	jscode+="};\n";
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_all_controllers_js=function(modeldefs)
{
	var jscode="";
	jscode+="exports.controllers={\n";
	for (var i=0;i<modeldefs.length;i++) {
		var modeldef=modeldefs[i];
		jscode+=this.build_controller_js(modeldef,modeldefs,i+1==modeldefs.length);
	}
	jscode+="};\n";
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_js=function(modeldef,modeldefs)
{
	var jscode="";
//	jscode+="/*****************************************************************************/\n";
//	jscode+="// "+modeldef.name+"\n";
	jscode+="	r."+modeldef.name+"=function(site,h,isFromObject) {\n";
	jscode+="		this.PreInit(site,\""+modeldef.name+"\",h,isFromObject);\n";
	for (var i=0;i<modeldef.columns.length;i++) {
		var col=modeldef.columns[i];
		switch (col.datatype) {
			case "integer":
				if (col.avatar) {
					var colname=col.name.replace(/_id/,"_avatar_id");
					jscode+="		this.InitFileId('"+colname+"',isFromObject);\n";
				}
				break;
			case "file_id":
				jscode+="		this.InitFileId('"+col.name+"',isFromObject);\n";
				break;
			case "file_group_id":
				jscode+="		this.InitFileGroupId('"+col.name+"',isFromObject);\n";
				break;
		}
	}
	jscode+="		this.InitAuto(isFromObject,h,site);\n";
	jscode+="		this.Init(isFromObject,h,site);\n";
	jscode+="	};\n\n";
	jscode+="	r."+modeldef.name+".prototype=new GenericModel();\n\n";
	if (modeldef.multilanguage_columns) {
		jscode+="	r."+modeldef.name+".prototype.multilanguage_columns="+JSON.stringify(modeldef.multilanguage_columns)+";\n";
	}
	jscode+="	r."+modeldef.name+".prototype.InitAuto=function(isFromObject,object,site) {\n";
	for (var i=0;i<modeldef.columns.length;i++) {
		var col=modeldef.columns[i];
		switch (col.datatype) {
			case "checkbox":
				jscode+="		if (!object['"+col.name+"']) this['"+col.name+"']=0;\n";
				break;
		}
		if (col.is_array && !col.i18n) {
			jscode+="		this.UnpackPgArray('"+col.name+"');\n";
		}
	}
	if (modeldef.multilanguage_columns) {
//		jscode+="		var kk='';\n";
//		jscode+="		for (var k in site) kk+=','+k;\n";
//		jscode+="		throw kk;\n";
		jscode+="		if (site) {\n";
		jscode+="			var l=site.use_language?site.use_language:site.language;\n";
		for (var i=0;i<modeldef.multilanguage_columns.length;i++) {
			var col=modeldef.multilanguage_columns[i];
			jscode+="			this[\""+col+"\"]=this[\""+col+"_\"+l];\n";
		}
		jscode+="		}\n";
		
	}
	jscode+="	};\n\n";
/*
	jscode+="	r."+modeldef.name+".prototype.toJSON=function() {\n";
	jscode+="		var ret={};\n";
	for (var i=0;i<modeldef.columns.length;i++) {
		var col=modeldef.columns[i];
		jscode+="		ret['"+col.name+"']=this['"+col.name+"'];\n";
		switch (col.datatype) {
			case "file_id":
				jscode+="		ret['"+col.name+"_url']=this['"+col.name+"_url'];\n";
				jscode+="		ret['"+col.name+"_name']=this['"+col.name+"_name'];\n";
				jscode+="		ret['"+col.name+"_name_or_filename']=this['"+col.name+"_name_or_filename'];\n";
				break;
		}
		if (col.fk) {var fkname=col.name.replace(/_id$/,'');jscode+="		ret['"+fkname+"_name']=this['"+fkname+"_name'];\n";}
	}
	if (modeldef.hier) jscode+="		ret.level=this.level;\n";
	jscode+="		return ret;\n";
	jscode+="	}\n";
*/
	jscode+="	r."+modeldef.name+".prototype.TableName='"+modeldef.table+"';\n";
	var clist=[];
	var jslist=[];
	var has_level=0;
	for (var i=0;i<modeldef.columns.length;i++) {
		var col=modeldef.columns[i];
		jslist.push(col.name);
		var th={name:col.name,datatype:col.datatype};
		clist.push(th);
		switch (col.datatype) {
			case "file_id":
				jslist.push(col.name+"_url");
				jslist.push(col.name+"_name");
				jslist.push(col.name+"_name_or_filename");
				break;
		}
		if (col.name=="parent_id") has_level=1;
		if (col.fk) {
			var fkname=col.name.replace(/_id$/,'');
			if (col.fk_colname) {
				var arrfk=col.fk_colname;
				if ( !(arrfk.constructor==Array)) arrfk=[arrfk];
				for (var jj=0;jj<arrfk.length;jj++) {
					jslist.push(fkname+"_"+arrfk[jj]);
				}
//				throw this.space.Dumper({col:col,jslist:jslist});
			} else {
				jslist.push(fkname+"_name");
			}
			th.fk=col.fk;
		}
	}
	if (modeldef.hier || has_level) jslist.push("level");
	if (modeldef.toJSON_add) for (var j=0;j<modeldef.toJSON_add.length;j++) jslist.push(modeldef.toJSON_add[j]);
	
	jscode+="	r."+modeldef.name+".prototype.Columns="+JSON.stringify(clist)+";\n";
	jscode+="	r."+modeldef.name+".prototype.toJSONlist="+JSON.stringify(jslist)+";\n";

	for (var i=0;i<modeldef.columns.length;i++) {
		var col=modeldef.columns[i];
		if (col.datatype=="file_group_id") jscode+=this.build_model_js_file_group(modeldef,modeldefs,col);
		if (col.fk) jscode+=this.build_model_js_fk(modeldef,modeldefs,col);

	}
	jscode+="\n\n";
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_controller_js=function(modeldef,modeldefs,islast)
{
	var jscode="";
	var col_by_name=false;
	var save_params="";
	modeldef.columns.forEach(function(x) { col_by_name[x.name]=x;});
	var ob="";
	if (modeldef.admin_list_ordering) {
		ob="'"+modeldef.admin_list_ordering+"'";
	} else if (col_by_name.ordering) {
		ob="'main.ordering'";
	} else {
		ob="'main.id'";
	}
	var admin_list_filters="";
	modeldef.columns.forEach(function(c) {
		if (c.through) {
			if (save_params) save_params+="\"&"; else save_params+="+\"?";
			save_params+=c.name;
			save_params+="=";
			save_params+="\"+item."+c.name;
		}
		if (c.admin_list_filter) {
			admin_list_filters+='				if (space.fields.'+c.name+') filters.'+c.name+'=space.fields.'+c.name+';\n';
		}
	});
	jscode+='	admin_auto_'+modeldef.name+': function(space) {\n';
	jscode+='		if (!space.roles.'+(modeldef.admin_role||'pages')+') return "-";\n';
	jscode+='		var mode=space.fields.mode||"list";\n';
	jscode+='		switch (mode) {\n';
	jscode+='			case "list":\n';
	jscode+='				var filters={order_by: '+ob+'};\n';
	jscode+=admin_list_filters;
	jscode+='				space.'+modeldef.name_many+'=space.mfact.list'+(modeldef.hier?"_hier":"")+'("'+modeldef.name+'",filters);\n';
	jscode+='				return space.views.process("_models/'+modeldef.name+'/admin_"+mode,space);\n';
	jscode+='				break;\n';
	jscode+='			case "edit":\n';
	jscode+='				if (space.fields.id) {\n';
	jscode+='					space.'+modeldef.name+'=space.mfact.get("'+modeldef.name+'",space.fields.id);\n';
	jscode+='				} else {\n';
	var tmph={};
	modeldef.columns.forEach(function(c) {
		if (!c.def_value) return;
		tmph[c.name]=c.def_value;
	});
	jscode+='					space.'+modeldef.name+'='+JSON.stringify(tmph)+';\n';
	jscode+='				}\n';

	modeldef.columns.forEach(function(c) {
		if (c.fk && c.fk_select) {
			if (c.fk_hier) {
				jscode+='				space.select_'+c.fk+'=space.site.sql.execute_and_fetch_h_single("'+c.fk_select+'");\n';
			} else {
				jscode+='				space.select_'+c.fk+'=space.site.sql.execute_and_fetch_single("'+c.fk_select+'");\n';
			}
			if (c.add_null) jscode+='				space.select_'+c.fk+'.unshift({id:"",name:""});\n';
		} else if (c.fk) {
		}
	});
	jscode+=this.build_controller_js_footer(modeldef,modeldefs,islast,"edit");
	jscode+='				return space.views.process("_models/'+modeldef.name+'/admin_"+mode,space);\n';
	jscode+='				break;\n';
	jscode+='			case "'+(modeldef.admin_mode_save||'save')+'":\n';
	jscode+='			case "'+(modeldef.admin_mode_save_and_continue||'save_only')+'":\n';
	jscode+='				var item;\n';
	jscode+='				if (space.fields.id) {\n';
	jscode+='					item=space.mfact.get("'+modeldef.name+'",space.fields.id);\n';
	jscode+='					item.UpdateFromObject(space.fields);\n';
	jscode+='				} else{\n';
	jscode+='					item=space.mfact.from_object("'+modeldef.name+'",space.fields);\n';
	modeldef.columns.forEach(function(c) {
		if (c.name=="user_cr_id") {
			jscode+='					item.user_cr_id=space.uid;\n';
		}
	});
	jscode+='				}\n';
	modeldef.columns.forEach(function(c) {
		if (c.uploader) {
			jscode+='				space.functions.'+c.uploader+'(space,item);\n';
		}
	});
	jscode+='				item.Save();\n';
	jscode+=this.build_controller_js_footer(modeldef,modeldefs,islast,"save");
	jscode+='				return (mode=="'+(modeldef.admin_mode_save_and_continue||'save_only')+'")?JSON.stringify({call:"ajajform_frm_set_id",name:"id",id:item.id}):JSON.stringify({redirect:{url: space.action'+save_params+'}});\n';
	jscode+='				break;\n';
/*	jscode+='			case "delete":\n';
	jscode+='				space.item=space.mfact.get("'+modeldef.name+'",space.fields.id);\n';
	jscode+='				return space.views.process("_models/'+modeldef.name+'/admin_"+mode,space);\n';
	jscode+='				break;\n';*/
	jscode+='			case "delete":\n';
	jscode+='				space.mfact.del_one("'+modeldef.name+'",space.fields.id);\n';
	jscode+='				return JSON.stringify({redirect:{url: space.action}});\n';
	jscode+='				break;\n';
	jscode+='			case "reorder":\n';
	jscode+='				space.functions.reorder_grids(space,"_models/'+modeldef.name+'/");\n';
	jscode+='				return JSON.stringify({});\n';
	jscode+='				break;\n';
	jscode+='		}\n';
	jscode+='	}';
	if (!islast) jscode+=",";
	jscode+="\n\n";
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_controller_js_footer=function(modeldef,modeldefs,islast,mode)
{
//	var mode1= (mode=="save_only")?"save":mode;
	var mode1= (mode=="save_only" || mode=="save")?"edit":mode;
	if (!modeldef[mode1+"_footer"]) return "";
	var jscode="";
//	throw this.space.Dumper(modeldef[mode1+"_footer"]);
	for (var i=0;i<modeldef[mode1+"_footer"].length;i++)
		jscode+=this.build_controller_js_footer_item(modeldef,modeldefs,islast,mode,modeldef[mode1+"_footer"][i]);
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_controller_js_footer_item=function(modeldef,modeldefs,islast,mode,fi)
{
	var jscode="";
	switch (fi.type+"-"+mode) {
		case "checkboxlist-edit":
			jscode+='				space.'+fi.name+'={\n';
			jscode+='					available: space.mfact.list("'+fi.model.available_name+'",'+JSON.stringify(fi.model.available_href)+'),\n';
			jscode+='					checked: space.mfact.ids("'+fi.model.name+'",{'+fi.model.limit+':space.'+modeldef.name+'.id||0},"'+fi.model.fk+'")\n';
			jscode+='				};\n';
			break;
		case "checkboxlist-save":
			jscode+='				space.functions.save_checkboxlist(space,"'+fi.model.name+'","'+fi.name+'-","'+fi.model.fk+'",{'+fi.model.limit+':item.id},{'+fi.model.limit+':item.id});\n';
			break;
		case "function-edit":
		case "function-save":
			jscode+='				space.functions.'+fi.name+'(space,"'+mode+'",{name:"'+modeldef.name+'"';
			if (mode=="save") jscode+=",item:item";
			jscode+='});\n';
//			throw jscode;
			break;
		default:
			jscode+="/* unknown footer "+fi.type+"/"+mode+"*/\n";
			throw " unknown footer "+fi.type+"/"+mode+"\n";
	}
	return jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_js_file_group=function(modeldef,modeldefs,col)
{
	var m_jscode="";
	var fname=col.name.replace(/_id$/,'');
	m_jscode+="	r."+modeldef.name+".prototype."+fname+"=function() {\n";
	m_jscode+="		return this.CachedFileGroup('"+col.name+"');\n";
	m_jscode+="	}\n\n";
	m_jscode+="	r."+modeldef.name+".prototype."+fname+"_FF=function() {\n";
	m_jscode+="		return this.CachedFileGroupFirstFile('"+col.name+"');\n";
	m_jscode+="	}\n";
	return m_jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_js_fk=function(modeldef,modeldefs,col)
{
	var m_jscode="";
	var fk=col.fkey_model;
	if (!fk) {
		for (var j=0;j<modeldefs.length;j++) {
			if (modeldefs[j].table.toLowerCase()==col.fk.toLowerCase()) fk=modeldefs[j].name;
		}
	}
	if (!fk) return "";
	var fkname=col.name.replace(/_id$/,'')+"_FK";
	m_jscode+="	r."+modeldef.name+".prototype."+fkname+"=function() {\n";
	m_jscode+="		if (!this."+col.name+") return null;\n";
	m_jscode+="		return this.Site.mfact.get('"+fk+"',this."+col.name+");\n";
	m_jscode+="	}\n";
	return m_jscode;
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql=function(modeldef)
{
	this.build_model_sql_get(modeldef);
	this.build_model_sql_count(modeldef);
	this.build_model_sql_insert(modeldef);
	this.build_model_sql_update(modeldef);
	this.build_model_sql_delete(modeldef);
	this.build_model_sql_delete_list(modeldef);
	this.build_model_sql_maxid(modeldef);
	this.build_model_sql_list(modeldef);
	this.build_model_sql_exists(modeldef);
	this.build_model_sql_get_ordering(modeldef);
	this.build_model_sql_set_ordering(modeldef);
}

/*****************************************************************************/
ModelBuilder.prototype.save_file_if_changed=function(filename,data)
{
	var f=new fs.File(filename);
	if (f.exists()) {
		f.open("r");
		var contents=f.read();
		f.close();
		if (contents.toString("utf-8")==data) return;
	}
	try {
		f.open("w");
		f.write(data);
		f.close();
	} catch(e) {
		throw "ModelBuilder.prototype.save_file_if_changed("+filename+") - "+e;
	}
}

/*****************************************************************************/
ModelBuilder.prototype.save_sql=function(modeldef,mode,sqlcode)
{
	var basepath=this.paths.sql+"_models/";
	var filename=modeldef.name+"/"+mode+".sql";
	this.space.functions.mkdir_rec(this.paths.sql,"_models/"+filename,1);
	this.save_file_if_changed(basepath+filename,sqlcode);
}

/*****************************************************************************/
ModelBuilder.prototype.build_sql_colvalue_fetch=function(tablename,col)
{
	function atom_array(colname,asname,is_array,asname_force) {
		if (is_array) {
			return "get_lang_str("+colname+",:lang_id) as "+(asname||asname_force);
		}
		if (asname) return colname+" as "+asname;
		return colname;
	}

	var r="";
	switch (col.datatype) {
		case "integer":
		case "smallint":
		case "numeric":
		case "double precision":
		case "text":
		case "varchar":
		case "character varying":
			r+=atom_array(tablename+"."+col.name,null,col.i18n&&col.is_array,col.name);
			break;
		case "checkbox":
			r+=tablename+"."+col.name;
			break;
		case "timestamp without time zone":
			r+="to_char("+tablename+"."+col.name+",'DD.MM.YYYY HH24:MI:SS') as "+col.name;
			break;
		case "date":
			r+="to_char("+tablename+"."+col.name+",'DD.MM.YYYY') as "+col.name;
			break;
		case "time":
		case "time without time zone":
			r+="to_char("+tablename+"."+col.name+",'HH24:MI:SS') as "+col.name;
			break;
		case "file_id":
			r+=tablename+"."+col.name;
			r+=",\n\t"+col.fktid+".file_group_id as "+col.name+"_file_group_id";
			r+=",\n\t"+col.fktid+".ext as "+col.name+"_ext";
			r+=",\n\t"+col.fktid+".name as "+col.name+"_name";
			r+=",\n\t"+col.fktid+".orig_filename as "+col.name+"_orig_filename";
			break;
		case "file_group_id":
			r+=tablename+"."+col.name;
			break;
		default:
			throw("Unknown datatype "+col.datatype+"\n");
	}
	if (col.fktid && col.datatype!="file_id" && !col.nofk) {
		var fk=col.name.replace(/_id$/,'');
		if (col.fk_colname) {
			if (col.fk_colname.constructor==Array) {
				col.fk_colname.forEach(function(x) {
					r+=",\n\t"+atom_array(col.fktid+"."+x,fk+"_"+x,col.i18n&&col.is_array)+"/* v1 */";
					//r+=",\n\t"+col.fktid+"."+x+" as "+fk+"_"+x;
				});
			} else {
				r+=",\n\t"+atom_array(col.fktid+"."+col.fk_colname,fk+"_name",col.i18n&&col.is_array)+"/* v2 */";
				//r+=",\n\t"+col.fktid+"."+col.fk_colname+" as "+fk+"_name";
			}
		} else if (col.fk=="tusers") {
			r+=",\n\t"+col.fktid+".calc_full_name as "+fk+"_name";
			if (col.avatar) {
				var fktid2=1*col.fktid.replace(/t/,"")+1;
				var prename=fk;
				r+=",\n\tt"+fktid2+".id as "+prename+"_avatar_id";
				r+=",\n\tt"+fktid2+".file_group_id as "+prename+"_avatar_id_file_group_id";
				r+=",\n\tt"+fktid2+".ext as "+prename+"_avatar_id_ext";
				r+=",\n\tt"+fktid2+".name as "+prename+"_avatar_id_name";
				r+=",\n\tt"+fktid2+".orig_filename as "+prename+"_avatar_id_orig_filename";
			}
		} else if (col.fk_colname_auto) {
			r+=",\n\t"+atom_array(col.fktid+"."+col.fk_colname_auto,fk+"_name",col.i18n&&col.is_array)+"/* v3 */";
			//r+=",\n\t"+col.fktid+"."+col.fk_colname_auto+" as "+fk+"_name";
		} else {
			r+=",\n\t"+atom_array(col.fktid+".name",fk+"_name",col.i18n)+"/* v4 */";
			//if (col.fk_colname_exists) r+=",\n\t"+col.fktid+".name as "+fk+"_name";
		}
	}
	return r;
}

/*****************************************************************************/
ModelBuilder.prototype.build_sql_colvalue_save=function(col)
{
	switch (col.datatype) {
		case "integer":
		case "numeric":
		case "smallint":
		case "double precision":
		case "text":
		case "file_id":
		case "file_group_id":
		case "character varying":
		case "varchar":
			return ":"+col.name;
		case "checkbox":
			return "coalesce(:"+col.name+",0)";
		case "timestamp without time zone":
			return "to_date(:"+col.name+",'DD.MM.YYYY HH24:MI:SS')";
		case "date":
			return "to_date(:"+col.name+",'DD.MM.YYYY')";
		case "time":
		case "time without time zone":
			return "to_date(:"+col.name+",'HH24:MI:SS')";
		default:
			throw("Unknown datatype "+col.datatype+"\n");
	}
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_get=function(modeldef)
{
	return this.build_model_sql_select(modeldef,"get");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_count=function(modeldef)
{
	return this.build_model_sql_select(modeldef,"count");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_list=function(modeldef)
{
	return this.build_model_sql_select(modeldef,"list");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_exists=function(modeldef)
{
	return this.build_model_sql_select(modeldef,"exists");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_get_ordering=function(modeldef) {
	this.save_sql(modeldef,"get_ordering","select ordering from "+modeldef.table+" where id=:id");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_set_ordering=function(modeldef) {
	this.save_sql(modeldef,"set_ordering","update "+modeldef.table+" set ordering=:ordering where id=:id");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_select=function(modeldef,mode)
{
	var r="select\n";
	switch (mode) {
		case "exists":
			r+="	1";
			break;
		case "count":
			r+="	count(*) as cnt";
			break;
		case "list":
		case "get":
			for (var i=0;i!=modeldef.columns.length;i++) {
				var c=modeldef.columns[i];
				if (c['skip_'+mode]) continue;
				if (i) r+=",\n";
				r+="	"+this.build_sql_colvalue_fetch("main",c);
			}
			break;
	}
	r+="\nfrom\n";
	r+="	"+modeldef.table+" main"+modeldef.joins;
	switch (mode) {
		case "get":
			r+="\nwhere\n	main.id=:id\n";
			break;
		case "list":
		case "count":
			r+="\nwhere\n	1=1/*where*/";
			break;
		case "exists":
			r+="\nwhere\n	1=1/*where*/";
			r+="\nlimit 1";
			break;
	}
	this.save_sql(modeldef,mode,r);
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_insert=function(modeldef)
{
	var r="insert into "+modeldef.table+" (";
	var r2=")\n	values (";
	var cnt=0;
	for (var i=0;i!=modeldef.columns.length;i++) {
		var c=modeldef.columns[i];
		if (c.skip_insert) continue;
		if (c.name=="id") continue;
		if (cnt) {r+=",";r2+=",";}
		r+=c.name;
		if (c.is_array && c.i18n) {r+="[coalesce(:lang_id,1)]";}
		r2+=this.build_sql_colvalue_save(c);
		cnt++;
	}
	this.save_sql(modeldef,'insert',r+r2+")");
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_update=function(modeldef)
{
	var r="update "+modeldef.table+" \nset\n";
	var cnt=0;
	for (var i=0;i!=modeldef.columns.length;i++) {
		var c=modeldef.columns[i];
		if (c.skip_update) continue;
		if (c.name=="id") continue;
		if (cnt) {r+=",\n";}
		if (c.name=="date_mo") {
			r+="	"+c.name+"=now()";
		} else {
			r+="	"+c.name;
			if (c.is_array && c.i18n) {r+="[coalesce(:lang_id,1)]";}
			r+="="+this.build_sql_colvalue_save(c);
		}
		cnt++;
	}
	r+="\nwhere id=:id\n";
	this.save_sql(modeldef,'update',r);
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_delete_list=function(modeldef)
{
	this.save_sql(modeldef,'delete_list','delete from '+modeldef.table+' main where 1=1/*where*/\n');
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_delete=function(modeldef)
{
	this.save_sql(modeldef,'delete','delete from '+modeldef.table+' where id=:id\n');
}

/*****************************************************************************/
ModelBuilder.prototype.build_model_sql_maxid=function(modeldef)
{
//	this.save_sql(modeldef,'maxid','select max(id) as id from '+modeldef.table+'\n');
	this.save_sql(modeldef,'maxid',"select currval(pg_get_serial_sequence('"+modeldef.table.toLowerCase()+"','id')) as id\n");
}

/*****************************************************************************/
ModelBuilder.prototype.fetch_table_columns=function(modeldef)
{
	var table=modeldef.table;
	var tinfo=this.site.sql.execute_and_fetch_one("pg_tableinfo/get",{table: table});
	if (!tinfo) {
		system.stderr.write("Table not found - "+table+", skipping\n");
		return undefined;
	}
	var columns=this.site.sql.execute_and_fetch("pg_tableinfo/columns",{id: tinfo.oid});
	var cols_hash={};
	for (var i=0;i<columns.length;i++) {
		var c=columns[i];
		var colsrc;
		if (modeldef.column_patches) colsrc=modeldef.column_patches[c.name];
		cols_hash[c.name]=c;
		c.is_array=false;
		if (c.datatype.match(/\[\]/)) {c.is_array=1;c.datatype=c.datatype.replace(/\[\]/,"");}
		var asize=c.datatype_full.match(/\((\d+)\)/);
		if (asize) c.datatype_size=asize[1];
		if (c.datatype=="character varying") c.datatype="varchar";
		switch (c.name) {
			case "enabled":
				c.datatype="checkbox";
				break;
			case "user_cr_id":
				c.readonly_edit=1;
				break;
			case "date_cr":
				c.skip_insert=1;
				c.skip_update=1;
				c.readonly_edit=1;
			case "date_mo":
				c.skip_insert=1;
				c.readonly_edit=1;
				break;
			case "ordering":
				c.skip_insert=1;
				c.skip_update=1;
				if (! ("skip_list" in c)) c.skip_list=1;
				c.skip_edit=1;
		}
		switch (c.datatype) {
			case "tsvector":
				c.skip_insert=1;
				c.skip_update=1;
				c.skip_list=1;
				c.skip_get=1;
				break;
		}
		if (colsrc) for (var k in colsrc) c[k]=colsrc[k];
	}
	var fktid=1;
	var fks=this.site.sql.execute_and_fetch("pg_tableinfo/foreign_keys",{id: tinfo.oid});
	modeldef.joins="";
	for (var i=0;i<fks.length;i++) {
		var arr;
		if (!(arr=fks[i].condef.match(/^FOREIGN KEY \((\w+)\) REFERENCES (\w+)\(id\)$/))) throw("Could not match "+fks[i].condef);
		var col=cols_hash[arr[1]];
		switch (arr[2]) {
			case "tfiles":
				col.datatype="file_id";
				break;
			case "tfile_groups":
				col.datatype="file_group_id";
				break;
			default:
				break;
		}
		col.fk=arr[2];
		col.fktid="t"+fktid;
		if (!col.fk_colname && col.fk=="tusers") {// Check if .name exists
			col.fk_colname_auto="calc_full_name";
			col.fk_colname_exists=true;
		} else if (!col.fk_colname) {// Check if .name exists
			var tinfo2=this.site.sql.execute_and_fetch_one("pg_tableinfo/get",{table: col.fk});
			if (!tinfo2) throw("FK Table not found - "+col.fk);
			var has_name=false;
			var like_name;
			this.site.sql.execute_and_fetch("pg_tableinfo/columns",{id: tinfo2.oid}).forEach(function(c) {
				if (c.name=="name") has_name=true;
				if (c.name.match(/name_|_name/) && !like_name) like_name=c.name;
			});
			if (!has_name && like_name) col.fk_colname_auto=like_name;
			col.fk_colname_exists=has_name;
		}
		modeldef.joins+="\n	left join "+arr[2]+" t"+fktid+" on t"+fktid+".id=main."+arr[1];
		fktid++;
		if (col.avatar && col.fk=="tusers") {
			modeldef.joins+="\n	left join Tfiles t"+fktid+" on t"+fktid+".id=t"+(fktid-1)+".avatar_id";
			fktid++;
		}
	}
	return columns;
}

/*****************************************************************************/
exports.ModelBuilder=ModelBuilder;

/*****************************************************************************/

